#-*- encoding:utf-8 -*-
import select
import socket
import sys
import time
#import logging
import threading
import os
from common import *
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
     ENOTCONN, ESHUTDOWN, EINTR, EISCONN, errorcode

class ETimeout(Exception):
    def __str__(self):

        return "%d Network Timeout"%EN_CONNECTIMEOUT

class ENetworkException(Exception):
     def __str__(self):

        return "%d Unknown NetworkException"%EN_NETUNKNOWNNETEXCEPTION

class ENetworkClosed(Exception):
     def __str__(self):

        return "%d Network has been closed gracefully"%EN_CONNECTCLOSED

class EConnected(Exception):
     def __str__(self):

        return "%d the address has been connected"%EN_CONNECTEDALREADY

class EDisConnected(Exception):
     def __str__(self):

        return "%d the connction has been  broken"%EN_DISCONNECTED

def read(obj):
    try:
        obj.handle_read_event()
    except Exception,e:
        obj.handle_error(e)
        obj.log.exception("read error")



def write(obj):
    try:
        obj.handle_write_event()
    except Exception,e:
        obj.handle_error(e)
        obj.log.exception("write error")

def _exception (obj):
    try:
        obj.handle_expt_event()
    except Exception,e:
        obj.handle_error(e)
        obj.log.exception("network exception")

"""
To use different sockType and poller, set the activeSockType, default is 0
"""

class Poller(object):
    def __init__(self,owner):
        self.owner = owner
        self.log = owner.log
        self.map = self.owner.map
    def poll(self):
        pass


                
class SockSelector(Poller):
    def __init__(self,owner):
        super(SockSelector,self).__init__(owner) 
        self.timeout = owner.timeout
        
    def poll(self):
        
        r = []; w = []; e = []
        for fd, channel in self.map.items():
            if channel._fileno:
                
                    r.append(fd)
                
                    w.append(fd)
              
                    e.append(fd)
            else:#it means
                self.log.debug("the socket has been closed, so remove %d from the map",fd)
                del  self.map[fd]

        if [] == r == w == e:
            return
        else:
            try:
                r, w, e = select.select(r, w, e, self.timeout)
            except select.error, err:
                if err[0] != EINTR:
                    raise
                else:
                    return
            
        
        for fd in r:
            channel = self.map.get(fd)
            if channel is None:
                continue
            channel.handle_read_event()

        for fd in w:
            channel = self.map.get(fd)
            if channel is None:
                continue
            channel.handle_write_event()

        for fd in e:
            channel = self.map.get(fd)
            if channel is None:
                continue
            channel.handle_expt_event()
    def _checkWriteable(cls,fileno,timeout):
        w=[fileno]
        try:
            r, w, e = select.select([], w, [],timeout)
        except select.error, err:
            if err[0] != EINTR:
                raise
            else:
                return False
        if w:
            return True
        else:
            return False
    def _checkReadable(cls,fileno,timeout):

        r=[fileno]
        try:
            r, w, e = select.select(r, [], [],timeout)
        except select.error, err:
            if err[0] != EINTR:
                raise
            else:
                return False
        if r:
            return True
        else:
            return False
    checkReadable  = classmethod(_checkReadable)
    checkWriteable = classmethod(_checkWriteable)

FakeFileIO=200000 #impossible sock port 
FakeFileIOlock= threading.Condition()
def getFakeFileIO(owner):
    with AutoLock(owner,FakeFileIOlock):
        global FakeFileIO
        FakeFileIO += 1
        return FakeFileIO
    
EBT_DATA    = 1
EBT_CONNECT = 2


bufSockList={} 
class BufferSock(object):
    def __init__(self,owner):  
        self.log = owner.log         
        self.owner = owner
        self.inBuf=[] #(id,type,data)
        self._fileno = getFakeFileIO(self)
        self.address=["%d"%self._fileno,self._fileno]
        self.peerID = None
        self.poller = owner.poller
        self.bufSockList = bufSockList
        self.lock= threading.Condition()
        
    def setblocking(self,value):
        pass
    def setsockopt(self,fakeType,fakeType2,fakeType3):
        pass
    def getsockopt(self,fakeType,fakeType2):
        return 1
    def listen(self, num):
        pass
                
    def accept(self):
        
        
        newSock = BufferSock(self.owner)
        buf = self.internalFetchOne()
        newSock.peerID = buf[0]
        self.poller.addOneBufSock(newSock._fileno,newSock,"accept from %d"%newSock.peerID)
        if self.bufSockList.has_key(newSock.peerID):
            self.bufSockList[newSock.peerID].peerID = newSock._fileno
        
        return newSock,tuple(newSock.address)
    
    def bind(self, addr):
        
        self.address = addr
        self._fileno = addr[1]
        self.poller.addOneBufSock(self._fileno,self,"bind")
        
    def connect_ex(self,address):
        
        self.poller.addOneBufSock(self._fileno,self,"connect to (%s,%d)"%address)
        id = address[1]
        if self.bufSockList.has_key(id):
            sock = self.bufSockList[id]
            sock.internalRecv((self._fileno,EBT_CONNECT,"connect"))
            return True
        else:
            return False
    
    def internalFetchOne(self):
        
        with AutoLock(self,self.lock):#,"sr:pop"):
            buf = self.inBuf.pop()
            if not buf:
                raise Exception,"%d no buf, unreadable"%self._fileno
            """
            if self._fileno==13917841776:
                if buf[1]==EBT_CONNECT:
                    self.log.debug("connectcheckFetch: from %d,len=%d"%(buf[0],len(self.inBuf)))
            """
            return buf
    def internalRecv(self,data):
        
        with AutoLock(self,self.lock):#,"sr:recv"):
            self.inBuf.insert(0, data)
            """
            if self._fileno==13917841776:
                if data[1]==EBT_CONNECT:
                    self.log.debug("connectcheckRecv: from %d,len=%d"%(data[0],len(self.inBuf)))
            """
    def sendall(self,data):
        self.bufSockList[self.peerID].internalRecv((self._fileno,EBT_DATA,data))
    
    def isReadable(self):
        
        with AutoLock(self,self.lock):
            return len(self.inBuf)>0
        
    def recv(self,size):
        
            buf = self.internalFetchOne()
            if buf[1]!=EBT_DATA:
                raise Exception,"mess up data type=%d"%sbuf[1]
            return buf[2]
    
    def close(self):
        self.poller.deleteBufSock(self._fileno)
        self._fileno = 0
        
    def getpeername(self):
        return self.address[0]
   
    def fileno(self):
        return self._fileno
    
class BufferPoller(Poller):
    def __init__(self,owner):
        super(BufferPoller,self).__init__(owner)  
        self.bufSockList=bufSockList#id:sock
        self.lock= threading.Condition()
        
    def poll(self):
        sockQueue = self.map.values()
        keys = self.map.keys()
        for channel in sockQueue:
            readable= channel.socket.isReadable()
            if readable:
                channel.handle_read_event()
            #self.log.debug("%d readable=%d"%(id,readable))
                 
    def addOneBufSock(self,id,sock,info):
        with AutoLock(self,self.lock,"addBufSock_%s:%d"%(info,id)):
            #print "addBufSock_%s:%d"%(info,id)
            self.bufSockList[id] = sock
            if info=="bind":# or info=="accept":
                for fileno,channel in self.map.items():
                    if channel.socket == sock:
                        self.owner.del_channel(channel)
                        channel._fileno = id
                        self.owner.add_channel(channel)
    
    def deleteBufSock(self,id):
       
        with AutoLock(self,self.lock,"deleteBufSock_%d"%id):
            #print "deleteBufSock_%d"%id
            if self.bufSockList.has_key(id):
                del self.bufSockList[id]
            
    def _checkWriteable(cls,fileno,timeout):
        
        #with AutoLock(self,self.lock):
            isok =  bufSockList.has_key(fileno) and bufSockList[fileno].peerID
            if not isok:
                time.sleep(timeout)
                isok =  bufSockList.has_key(fileno) and bufSockList[fileno].peerID
            return isok
    
    def _checkReadable(cls,fileno,timeout):
        #with AutoLock(self,self.lock):
            return bufSockList.has_key(fileno) and len(bufSockList[fileno])>0
        
    
    checkReadable  = classmethod(_checkReadable)
    checkWriteable = classmethod(_checkWriteable) 
    
    
sockTypeList=(
              (socket.socket,SockSelector),
              (BufferSock,BufferPoller)
             )
activeSockType=1 #@0 sock, 1 buffer
SockType   = sockTypeList[activeSockType][0]
PollerType = sockTypeList[activeSockType][1]

def CreateSock(type1,type2,owner):
    if activeSockType!=1:
        return SockType(socket.AF_INET, socket.SOCK_STREAM)
    else:
        return SockType(owner)

def IsUseBufSock():
    return activeSockType==1
    
class channelManager(Task):
    """
        class comment
    """

    def __init__(self,owner,timeout=0.1):

        super(channelManager,self).__init__(owner,"channelManager",timeout,loopCount=ALWAYS_RUN)
        self.map= {}
        self.timeout = timeout
        self.lock= threading.Condition()
        self.poller = PollerType(self)
        
    
    def run(self):

        with AutoLock(self,self.lock):
               self.poller.poll()



    def add_channel(self,channel):

        with AutoLock(self,self.lock,"add_channel %d"%channel._fileno):

            self.map[channel._fileno] = channel


    def del_channel(self, channel):

        with AutoLock(self,self.lock,"del_channel %d"%channel._fileno):

            fd = channel._fileno
            if self.map.has_key(fd):
                
                del self.map[fd]

    def exit(self):

        with AutoLock(self,self.lock,"force close the sockManager"):
            for channel in self.map.values():
                channel.close()
            self.map.clear()

    def handle_error(self,channel,exception):
        raise Exception,"not implemented, you must overwride handle_error"

    def handle_close(self,channel):
        raise Exception,"not implemented, you must overwride handle_close"



class channel(object):
    """
    Channel is a socket wrapper.
    It wraps all the socket function
    """
    def __init__(self,manager,sock):

        self.log= manager.log
     
        self.connected = False
        self.accepting = False #indicates it is a listner
        self.forceExit = False
        if sock:
            self.set_socket(sock)
        
        self.socket.setblocking(0)
        self.created = True
        self.manager = manager
        self.lock= threading.Condition()

    def set_socket(self, sock):
        self.socket = sock
        self._fileno = sock.fileno()
        self.log.debug("create_socket=%d"%self._fileno)

    def set_reuse_addr(self):

        self.socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR,
            self.socket.getsockopt(socket.SOL_SOCKET,
                                   socket.SO_REUSEADDR) | 1
            )


    # ==================================================
    # predicates for select()
    # these are used as filters for the lists of sockets
    # to pass to select().
    # ==================================================

   

    def checkReadable(self,timeout):
             
        return PollerType.checkReadable(self._fileno,timeout)
        

    def checkWriteable(self,timeout):
        
        
        if PollerType.checkWriteable(self._fileno,timeout):
            if not self.connected:
                self.connected = True
            return True
        else:
            return False
    # ==================================================
    # socket object methods.
    # ==================================================

    def listen(self, num):
        self.accepting = True
        #if os.name == 'nt' and num > 5: num = 1
        self.socket.listen(num)


    def bind(self, addr):
        self.socket.bind(addr)
       

    def connect(self, address,timeoutBySecond=10):

        if self.connected:
            raise EConnected

        self.log.debug("begin connect,%s:%d,timeout=%d",address[0],address[1],timeoutBySecond)

        #test = self.checkWriteable(0.1)
        self.socket.connect_ex(address)

        timeoutBySecond *=10
        while (not self.checkWriteable(0.1)) and timeoutBySecond>=0:

            timeoutBySecond -= 1
            #time.sleep(0.1)
            if(self.forceExit):
                self.log.debug("force exit connect,%s:%d"%address)
                return

        if not self.connected and timeoutBySecond<0:
                raise ETimeout#, "timeout to connect,%s:%d"%address
        self.log.debug("finish connect,%s:%d"%address)


    def accept(self):
        # XXX can return either an address pair or None
        try:
            conn, addr = self.socket.accept()
            return conn, addr
        except socket.error, why:
            if why[0] == EWOULDBLOCK:
                pass
            else:
                raise

    def send(self, data):
        #with AutoLock(self,self.lock,"sr:send sock%d"%self._fileno):#somehow,will deadlock, check more 20120208
             if self._fileno:
                try:
                    result = self.socket.sendall(data)
                    return result
                except socket.error, why:
                    if why[0] == EWOULDBLOCK:
                        return 0
                    else:
                        raise
                    return 0
             else:
                 return 0

    def recv(self, buffer_size):
        #with AutoLock(self,self.lock,"sr:read sock%d"%self._fileno):#,"readsock=%d"%self._fileno):
            if self._fileno:
                data = self.socket.recv(buffer_size)
                if not data:
                    # a closed connection is indicated by signaling
                    # a read condition, and having recv() return 0.
                    self.handle_error(ENetworkClosed())
                    return ''
                else:
                    return data
            else:
                return ''

    def close(self):
        self.forceExit = True
        with AutoLock(self,self.lock,"exit session,socket=%d"%self._fileno):
            if self.socket:
                self.connected = 0
                self.socket.close()
                self.socket = None
                self._fileno = 0

    # cheap inheritance, used to pass all other attribute
    # references to the underlying socket object.
    #def __getattr__(self, attr):
        #return getattr(self.socket, attr)

    def getpeername(self):
        return self.socket.getpeername()

    def handle_read_event(self):
        """
        Interface to the channel manager to dispatch the read events,
        accept and read
        """
        try:
            with AutoLock(self,self.lock):
                if self.socket:
                    if self.accepting:
                        # for an accepting socket, getting a read implies
                        # that we are connected
                        if not self.connected:
                            self.connected = True
                        #self.log.debug("before accepting")
                        self.handle_accept()
                    elif not self.connected:
                        raise Exception,"Impossible, not connected but readable"
                    else:
                        self.handle_read()
        except Exception,e:
                   
                    self.handle_error(e)        
                    self.log.exception("\nsock_%d-error:"%self._fileno)                
    def handle_write_event(self):
        
        """
        Interface to the channel manager to dispatch the write events
        only handle the connect event. Regarding the write event, because it is an
        initiative action, the caller needs to call checkWritable first,though in most of
        case, which is not necessary.
        """
        try:
             with AutoLock(self,self.lock):
                if self.socket:
                    if not self.connected:
                        self.handle_connect()
        except Exception,e:
                
             self.handle_error(e)        
             self.log.exception("\nsock_%d-error:"%self._fileno)              

    def handle_expt_event(self):
        """
        Interface to the channel manager to handle exception events
        """
        try:
            
            self.handle_expt()
        
        except Exception,e:
                   
            self.handle_error(e)        
            self.log.exception("\nsock_%d-error:"%self._fileno)  
                 
    def handle_error(self,exception):
         with AutoLock(self,self.lock):

            self.manager.handle_error(self,exception)

    def handle_expt(self):

        self.handle_error(ENetworkException())

    def handle_read(self):
        """
        should be overwrided 
        """
        raise Exception,'unhandled read event'

    def handle_write(self):
        pass #not used so far

    def handle_connect(self):
        self.log.debug("connect is done\n")
        self.connected = True

    def handle_accept(self):
        raise Exception,'unhandled accept event'

    def handle_close(self):
        self.manager.handle_close(self)

